package com.grechur.wanandroidcompose.widget.smartrefresh.indicator


import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.grechur.wanandroidcompose.widget.smartrefresh.SmartSwipeFooterState
import com.grechur.wanandroidcompose.widget.smartrefresh.SmartSwipeHeaderState
import com.grechur.wanandroidcompose.widget.smartrefresh.SmartSwipeRefreshState

/**
 * A class to encapsulate details of different indicator sizes.
 *
 * @param size The overall size of the indicator.
 * @param arcRadius The radius of the arc.
 * @param strokeWidth The width of the arc stroke.
 * @param arrowWidth The width of the arrow.
 * @param arrowHeight The height of the arrow.
 */
@Immutable
private data class SwipeIndicatorSizes(
    val size: Dp,
    val arcRadius: Dp,
    val strokeWidth: Dp,
    val arrowWidth: Dp,
    val arrowHeight: Dp,
)

/**
 * The default/normal size values for [SwipeRefreshIndicator].
 */
private val defaultSizes = SwipeIndicatorSizes(
    size = 40.dp,
    arcRadius = 7.5.dp,
    strokeWidth = 2.5.dp,
    arrowWidth = 10.dp,
    arrowHeight = 5.dp,
)

/**
 * The 'large' size values for [SwipeRefreshIndicator].
 */
private val largeSizes = SwipeIndicatorSizes(
    size = 56.dp,
    arcRadius = 11.dp,
    strokeWidth = 3.dp,
    arrowWidth = 12.dp,
    arrowHeight = 6.dp,
)

@Composable
fun SwipeRefreshIndicator(
    state: SmartSwipeRefreshState,
    isFooter: Boolean,
    modifier: Modifier = Modifier,
    fade: Boolean = true,
    scale: Boolean = false,
    arrowEnabled: Boolean = true,
    backgroundColor: Color = Color.White,
    contentColor: Color = Color.Black,
    shape: Shape = CircleShape,
    paddingValues: PaddingValues = PaddingValues(12.dp),
    largeIndication: Boolean = false,
    elevation: Dp = 6.dp,
    label: String = "Indicator"
) {
    val adjustedElevation = if (isFooter) {
        when {
            state.isLoading -> elevation
            state.indicatorOffset < -0.5f -> elevation
            else -> 0.dp
        }
    } else {
        when {
            state.isRefreshing -> elevation
            state.indicatorOffset > 0.5f -> elevation
            else -> 0.dp
        }
    }
    val sizes = if (largeIndication) largeSizes else defaultSizes
    val indicatorHeight = with(LocalDensity.current) { sizes.size.roundToPx() }

    val slingshot = if (isFooter) {
        rememberUpdatedSlingshot(
            offsetY = -state.indicatorOffset,
            maxOffsetY = -state.loadMoreTrigger,
            height = indicatorHeight,
        )
    } else {
        rememberUpdatedSlingshot(
            offsetY = state.indicatorOffset,
            maxOffsetY = state.refreshTrigger,
            height = indicatorHeight,
        )
    }

    val alphaState = remember {
        derivedStateOf {
            if ((state.indicatorOffset > 0f && !isFooter) || (state.indicatorOffset < 0f && isFooter)) {
                1f
            } else {
                0f
            }
        }
    }

    Box(
        modifier = modifier
            .fillMaxWidth()
            .padding(paddingValues),
        contentAlignment = Alignment.Center
    ) {
        Surface(
            modifier = Modifier
                .alpha(alphaState.value)
                .size(size = sizes.size)
                .graphicsLayer {
                    if (isFooter) {
                        val scaleFraction =
                            if (scale && state.footerState != SmartSwipeFooterState.Loading) {
                                val progress =
                                    state.indicatorOffset / state.loadMoreTrigger.coerceAtMost(-1f)

                                // We use LinearOutSlowInEasing to speed up the scale in
                                LinearOutSlowInEasing
                                    .transform(progress)
                                    .coerceIn(0f, 1f)
                            } else 1f

                        scaleX = scaleFraction
                        scaleY = scaleFraction

                    } else {
                        // Translate the indicator according to the slingshot
                        translationY = 0f

                        val scaleFraction =
                            if (scale && state.headerState != SmartSwipeHeaderState.Refreshing) {
                                val progress =
                                    state.indicatorOffset / state.refreshTrigger.coerceAtLeast(1f)

                                // We use LinearOutSlowInEasing to speed up the scale in
                                LinearOutSlowInEasing
                                    .transform(progress)
                                    .coerceIn(0f, 1f)
                            } else 1f

                        scaleX = scaleFraction
                        scaleY = scaleFraction
                    }
                },
            shape = shape,
            color = backgroundColor,
            elevation = adjustedElevation
        ) {
            val painter = remember { CircularProgressPainter() }
            painter.arcRadius = sizes.arcRadius
            painter.strokeWidth = sizes.strokeWidth
            painter.arrowWidth = sizes.arrowWidth
            painter.arrowHeight = sizes.arrowHeight
            painter.color = contentColor
            if (isFooter) {
                painter.arrowEnabled =
                    arrowEnabled && state.footerState != SmartSwipeFooterState.Loading
                val alpha = if (fade) {
                    (state.indicatorOffset / state.loadMoreTrigger).coerceIn(0f, 1f)
                } else {
                    1f
                }
                painter.alpha = alpha
            } else {
                painter.arrowEnabled =
                    arrowEnabled && state.headerState != SmartSwipeHeaderState.Refreshing
                val alpha = if (fade) {
                    (state.indicatorOffset / state.refreshTrigger).coerceIn(0f, 1f)
                } else {
                    1f
                }
                painter.alpha = alpha
            }

            painter.startTrim = slingshot.startTrim
            painter.endTrim = slingshot.endTrim
            painter.rotation = slingshot.rotation
            painter.arrowScale = slingshot.arrowScale

            // This shows either an Image with CircularProgressPainter or a CircularProgressIndicator,
            // depending on refresh state
            Crossfade(
                targetState = if (isFooter) {
                    state.footerState == SmartSwipeFooterState.Loading
                } else {
                    state.headerState == SmartSwipeHeaderState.Refreshing
                },
                animationSpec = tween(durationMillis = CrossfadeDurationMs)
            ) { refreshing ->
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ) {
                    if (refreshing) {
                        val circleSize = (sizes.arcRadius + sizes.strokeWidth) * 2
                        CircularProgressIndicator(
                            color = contentColor,
                            strokeWidth = sizes.strokeWidth,
                            modifier = Modifier.size(circleSize),
                        )
                    } else {
                        Image(
                            painter = painter,
                            contentDescription = "Indicator"
                        )
                    }
                }
            }
        }
    }
}

@Composable
internal fun Surface(
    modifier: Modifier = Modifier,
    shape: Shape = RectangleShape,
    color: Color = Color.White,
    border: BorderStroke? = null,
    elevation: Dp = 0.dp,
    content: @Composable () -> Unit
) {
    Box(
        modifier = modifier
            .surface(
                shape = shape,
                backgroundColor = color,
                border = border,
                elevation = elevation
            )
            .semantics(mergeDescendants = false) {}
            .pointerInput(Unit) {},
        propagateMinConstraints = true
    ) {
        content()
    }
}

private fun Modifier.surface(
    shape: Shape,
    backgroundColor: Color,
    border: BorderStroke?,
    elevation: Dp
) = this
    .shadow(elevation, shape, clip = false)
    .then(if (border != null) Modifier.border(border, shape) else Modifier)
    .background(color = backgroundColor, shape = shape)
    .clip(shape)

private const val CrossfadeDurationMs = 100
